C# 反射创建对象
C# 反射(Reflection)
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。
可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
它的缺点:
1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。 2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
typeof 关键字
typeof 运算符用于获取某个类型的 System.Type
实例。 typeof 运算符的实参必须是类型或类型形参的名称
// 这里定义一个方法(这个语法是 C# 的 Lambda)
void PrintType<T>() => Console.WriteLine(typeof(T));
Console.WriteLine(typeof(List<string>));
PrintType<int>();
PrintType<System.Int32>();
PrintType<Dictionary<int, char>>();
// Output:
// System.Collections.Generic.List`1[System.String]
// System.Int32
// System.Int32
// System.Collections.Generic.Dictionary`2[System.Int32,System.Char]
简单使用例
参考资料 C# 反射,通过类名、方法名调用方法
因为 C# 中一个命名空间可以在不同的文件里面,而且 C# 不像 Java 那样使用文件名称来约束类名,从而可以直接全类名创建对象,所以需要使用 命名空间 + 类名 来创建,不然会找不到类。
// 这里的 StudyCSharp.MyTestClassNamespace 就是命名空间 TestClass02 就是类名
// 注意,要给这个类一个无参构造,且别忘了用 public 修饰
string nspace = "StudyCSharp.MyTestClassNamespace.TestClass02";
Activator.CreateInstance(Type.GetType(nspace));
下面是具体实例
using System;
using System.Reflection;
class Test
{
// 无参数,无返回值方法
public void Method()
{
Console.WriteLine("Method(无参数) 调用成功!");
}
// 有参数,无返回值方法
public void Method(string str)
{
Console.WriteLine("Method(有参数) 调用成功!参数 :" + str);
}
// 有参数,有返回值方法
public string Method(string str1, string str2)
{
Console.WriteLine("Method(有参数,有返回值) 调用成功!参数 :" + str1 + ", " + str2);
string className = this.GetType().FullName; // 非静态方法获取类名
return className;
}
}
class Program
{
static void Main(string[] args)
{
string strClass = "Test"; // 命名空间+类名
string strMethod = "Method"; // 方法名
Type type; // 存储类
Object obj; // 存储类的实例
type = Type.GetType(strClass); // 通过类名获取同名类
obj = System.Activator.CreateInstance(type); // 创建实例
MethodInfo method = type.GetMethod(strMethod, new Type[] {}); // 获取方法信息
object[] parameters = null;
method.Invoke(obj, parameters); // 调用方法,参数为空
// 注意获取重载方法,需要指定参数类型
method = type.GetMethod(strMethod, new Type[] { typeof(string) }); // 获取方法信息
parameters = new object[] {"hello"};
method.Invoke(obj, parameters); // 调用方法,有参数
method = type.GetMethod(strMethod, new Type[] { typeof(string), typeof(string) }); // 获取方法信息
parameters = new object[] { "hello", "你好" };
string result = (string)method.Invoke(obj, parameters); // 调用方法,有参数,有返回值
Console.WriteLine("Method 返回值:" + result); // 输出返回值
// 获取静态方法类名
string className = MethodBase.GetCurrentMethod().ReflectedType.FullName;
Console.WriteLine("当前静态方法类名:" + className);
Console.ReadKey();
}
}
查看元数据
使用反射(Reflection)可以查看特性(attribute)信息。
System.Reflection
类的 MemberInfo
对象需要被初始化,用于发现与类相关的特性(attribute)。为了做到这点,可以定义目标类的一个对象,如下:
System.Reflection.MemberInfo info = typeof(MyClass);
下面的程序演示了这点:
using System;
[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{
public readonly string Url;
// Topic 是一个命名(named)参数
public string Topic {get; set;}
public HelpAttribute(string url) // url 是一个定位(positional)参数
{
this.Url = url;
}
}
[HelpAttribute("Information on the class MyClass")]
class MyClass
{
}
namespace AttributeAppl
{
class Program
{
static void Main(string[] args)
{
System.Reflection.MemberInfo info = typeof(MyClass);
object[] attributes = info.GetCustomAttributes(true);
for (int i = 0; i < attributes.Length; i++)
{
System.Console.WriteLine(attributes[i]);
}
Console.ReadKey();
}
}
}
当上面的代码被编译和执行时,它会显示附加到类 MyClass 上的自定义特性:
HelpAttribute
读取命名空间下的类
namespace StudyCSharp
{
class Program
{
static void Main(string[] args)
{
string nspace = "StudyCSharp.MyTestClassNamespace";
var q = from t in Assembly.GetExecutingAssembly().GetTypes()
where t.IsClass && t.Namespace == nspace
select t;
q.ToList().ForEach(t =>
{
Console.WriteLine("类名称为:" + t.Name);
// 取得全部构造方法(注意,这里只显示 public 修饰的)
foreach (ConstructorInfo info in t.GetConstructors())
{
Console.WriteLine("构造方法参数:");
// 打印参数
foreach (var parameter in info.GetParameters())
{
Console.Write(" 参数名称为:" + parameter.Name );
Console.Write(" 参数类型为:" + parameter.ParameterType);
}
Console.Write("\n");
}
Console.WriteLine("\n");
});
}
}
// 用来测试读取类
namespace MyTestClassNamespace
{
abstract class BaseTestClass
{
public abstract void printName();
}
class TestClass01 : BaseTestClass
{
public TestClass01(int age, string name)
{
// 这里随便创建一个和其它派生类不同的构造函数
}
public TestClass01(int age)
{
// 这里随便创建一个和其它派生类不同的构造函数
}
public override void printName()
{
Console.WriteLine("这是 TestClass01");
}
}
class TestClass02 : BaseTestClass
{
// 这里没有用 public 修饰,所以不显示
TestClass02(bool flag)
{
}
public override void printName()
{
Console.WriteLine("这是 TestClass02");
}
}
}
}
打印的结果为:
类名称为:BaseTestClass
类名称为:TestClass01
构造方法参数:
参数名称为:age 参数类型为:System.Int32 参数名称为:name 参数类型为:System.String
构造方法参数:
参数名称为:age 参数类型为:System.Int32
类名称为:TestClass02
取得继承基类的所有子类
static class ReflectionHelper
{
public static IEnumerable<T> CreateAllInstancesOf<T>()
{
return typeof (ReflectionHelper).Assembly.GetTypes() //获取当前类库下所有类型
.Where(t => typeof (T).IsAssignableFrom(t)) //获取间接或直接继承t的所有类型
.Where(t => !t.IsAbstract && t.IsClass) //获取非抽象类 排除接口继承
.Select(t => (T) Activator.CreateInstance(t)); //创造实例,并返回结果(项目需求,可删除)
}
}
使用带参的构造函数
参考资料 反射之动态创建对象 参考资料 C# 利用反射动态创建对象——带参数的构造函数和String类型
“反射”其实就是利用程序集的元数据信息。
反射可以有很多方法,编写程序时请先导入 System.Reflection
命名空间,假设你要反射一个 DLL 中的类,并且没有引用它(即未知的类型):
Assembly assembly = Assembly.LoadFile("程序集路径,不能是相对路径"); // 加载程序集(EXE 或 DLL)
object obj = assembly.CreateInstance("类的完全限定名(即包括命名空间)"); // 创建类的实例
若要反射当前项目中的类可以为:
Assembly assembly = Assembly.GetExecutingAssembly(); // 获取当前程序集
object obj = assembly.CreateInstance("类的完全限定名(即包括命名空间)"); // 创建类的实例,返回为 object 类型,需要强制类型转换
也可以为:
Type type = Type.GetType("类的完全限定名");
object obj = type.Assembly.CreateInstance(type);
上述描述中提到的三种方法其实都是大同小异的,核心就是通过 System.Reflection.Assembly
类型的 CreateInstance
方法创建实例。
那么简单的解释一下这种方法的原理:
- 找到要实例化的类所在的程序集,并将之实例为
System.Reflection.Assembly
类的对象 - 利用
System.Reflection.Assembly
类提供的 CreateInstance 方法,创建类的对象
但是上面那几种方法都只能创建无参构造函数
这里创建一个带参测试类
class Test
{
private string _strId;
public string ID
{
get { return _strId; }
set { _strId = value; }
}
public Test(string str)
{
_strId = str;
}
}
可以直接通过取得类型的 ConstructorInfo
来创建对象
// 使用的类是上面读取命名空间那节创建的类
static void Main(string[] args)
{
string nspace = "StudyCSharp.MyTestClassNamespace.TestClass01";
var mytype = Type.GetType(nspace);
ConstructorInfo[] gc = mytype.GetConstructors();
ConstructorInfo constructor = mytype.GetConstructor(new Type[2] {typeof(int), typeof(string)});
// 通过形参的数量,类型来取得不同的构造函数
//ConstructorInfo constructor = mytype.GetConstructor(new Type[1] {typeof(int)});
var obj = (BaseTestClass)constructor.Invoke(new object[2] {18, "张三"});
obj.printName();
}
如上所示,这种方式对参数的排序要求很高
总结:利用反射创建对象两种方法
1、利用 Activator.CreateInstance
,前提是调用对象的默认无参构造函数
2、利用构造器来动态创建对象
dynamic 关键字
上面的例子可以使用 dynamic
关键字继续优化
static void Main(string[] args)
{
/* ...... */
ConstructorInfo constructor = mytype.GetConstructor(new Type[2] {typeof(int), typeof(string)});
dynamic obj = constructor.Invoke(new object[2] {18, "张三"});
obj.printName();
}
可以发现无需再强转了,那这个神奇的关键字是什么呢?
C#4.0 引入了一个新类型 dynamic
。该类型是一种静态类型,但类型为 dynamic
的对象会跳过静态类型检查。
它指示动态类型。我们可以直接创建一个 dynamic
类型的变量,可以将任意对象赋值给它。如下所示:
dynamic dynVar1 = 1;
dynamic dynVar2 = new Object();
当我们在代码中使用了 dynamic
类型时,就是在告诉编译器关闭对该对象的运行时检查,而在运行时确定对象类型。比如,以下代码可以成功编译:
dynamic numericDyn = 80;
numericDyn.Greet();
但是在这段代码会抛出一个运行时异常 RuntimeBinderException,提示我们类型 int 不包含方法 Greet()
的定义。这说明了在底层, dynamic
变量类型在运行时仍然是确定的,也就是说,C#依然是静态类型化语言。当我们在一个 dynamic
变量上调用 GetType()
时,会输出该对象的实际类型。如下所示:
Console.WriteLine(numericDyn.GetType());
// Console prints System.Int32
var, object 和 dynamic
关键字 var
用于编译时类型推断。编译器在编译时自动确定 var
代表的类型。如下列代码:
var varInt = 1;
var varStr = "Hello, Vars!";
关闭优化编译,使用 .NET Reflector
反编译得到
int num = 1;
string str = "Hello, Vars!";
该关键字可以在编译时得到类型信息,于是可以进行 IntelliSense 和重构。
类型 object
是所有对象的基类,有着与 dynamic
相似的用法,但是必须进行强制转换才可以用子类的方法、属性等。如:
object objCat = new Cat();
objCat.Meow(); // CS1061 'object' does not contain a definition of 'Meow'
((Cat)objCat).Meow(); // OK
dynamic dynCat = new Cat();
dynCat.Meow(); // OK, but no IntelliSense
dynamic 简化反射
以前我们这样使用反射:
public class DynamicSample
{
public string Name { get; set; }
public int Add(int a, int b)
{
return a + b;
}
}
DynamicSample dynamicSample = new DynamicSample();
var addMethod = typeof(DynamicSample).GetMethod("Add");
//create instance 为了简化演示,这里没有使用反射再创建一个新的对象,而是直接沿用上面的对象 dynamicSample
// 可以发现这里 Invoke 还需要强转返回类型
int re = (int)addMethod.Invoke(dynamicSample, new object[] { 1, 2 });
现在,我们有了简化的写法:
// Invoke 返回的对象是 Object 类型
dynamic dynamicSample2 = constructor.Invoke();
// 这里省略了强转,直接调用
int re2 = dynamicSample2.Add(1, 2);